<?php

namespace App\Services\Settlement;

use App\Exceptions\InsufficientTenantBalanceException;
use App\Models\Settlement;
use App\Models\SubSettlement;
use App\Models\Tenant;
use App\Models\Transaction;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

class SettlementBatchPayService
{
    /**
     * 预扣余额并将子结算单标记为待发放。
     *
     * @param Settlement $settlement
     * @param Collection<int, SubSettlement> $subSettlements
     * @param int $tenantId
     * @param int $initiatorId
     * @param string $bankName
     * @return array{transaction: Transaction, amount: string, tenant_balance: string}
     */
    public function reserveBalanceForChunk(
        Settlement $settlement,
        Collection $subSettlements,
        int $tenantId,
        int $initiatorId,
        string $bankName
    ): array {
        if ($subSettlements->isEmpty()) {
            throw new InvalidArgumentException('子结算单批次不能为空');
        }

        return DB::transaction(function () use ($settlement, $subSettlements, $tenantId, $initiatorId, $bankName) {
            $tenant = Tenant::whereKey($tenantId)->lockForUpdate()->firstOrFail();

            $amount = $subSettlements->sum(function (SubSettlement $item) {
                return (float) $item->commission;
            });
            $amountFormatted = number_format($amount, 2, '.', '');

            Log::info('[BatchPay][Reserve] Calculated chunk info', [
                'settlement_id' => $settlement->id,
                'tenant_id' => $tenantId,
                'sub_settlement_ids' => $subSettlements->pluck('id')->all(),
                'amount' => $amountFormatted,
                'tenant_balance_before' => $tenant->account_balance,
            ]);

            if (bccomp($tenant->account_balance, $amountFormatted, 2) < 0) {
                Log::warning('[BatchPay][Reserve] Insufficient tenant balance', [
                    'settlement_id' => $settlement->id,
                    'tenant_id' => $tenantId,
                    'required_amount' => $amountFormatted,
                    'tenant_balance' => $tenant->account_balance,
                ]);

                throw new InsufficientTenantBalanceException(sprintf(
                    '租户余额不足，当前余额：%s，所需金额：%s',
                    $tenant->account_balance,
                    $amountFormatted
                ));
            }

            $subSettlementIds = $subSettlements->pluck('id')->all();

            SubSettlement::whereIn('id', $subSettlementIds)->update([
                'status' => SubSettlement::STATUS_PENDING_DISTRIBUTE,
                'status_description' => '等待银行批量代发',
                'bank_batch_id' => null,
                'distribute_started_at' => null,
                'distributed_at' => null,
            ]);

            $tenant->account_balance = bcsub($tenant->account_balance, $amountFormatted, 2);
            $tenant->save();

            Log::info('[BatchPay][Reserve] Tenant balance deducted', [
                'settlement_id' => $settlement->id,
                'tenant_id' => $tenantId,
                'deducted_amount' => $amountFormatted,
                'balance_after' => $tenant->account_balance,
            ]);

            $transaction = Transaction::create([
                'tenant_id' => $tenantId,
                'type' => Transaction::TYPE_EXPENSE,
                'sub_type' => Transaction::SUB_TYPE_SETTLEMENT,
                'amount' => $amountFormatted,
                'balance_after' => $tenant->account_balance,
                'landing_company' => $settlement->landing_company ?? '未配置落地公司',
                'status' => Transaction::STATUS_PROCESSING,
                'summary' => sprintf('结算单 %s 批量发放预扣', $settlement->settlement_number),
                'description' => sprintf('结算单 %s 子结算预扣款项', $settlement->settlement_number),
                'metadata' => [
                    'settlement_id' => $settlement->id,
                    'settlement_number' => $settlement->settlement_number,
                    'sub_settlement_ids' => $subSettlementIds,
                    'bank' => $bankName,
                ],
                'transaction_time' => Carbon::now(),
                'created_by' => $initiatorId,
            ]);

            Log::info('[BatchPay][Reserve] Transaction created', [
                'transaction_id' => $transaction->id,
                'transaction_number' => $transaction->transaction_number,
                'settlement_id' => $settlement->id,
                'tenant_id' => $tenantId,
            ]);

            return [
                'transaction' => $transaction,
                'amount' => $amountFormatted,
                'tenant_balance' => $tenant->account_balance,
            ];
        });
    }

    /**
     * 批量更新为发放中。
     *
     * @param Collection<int, SubSettlement> $subSettlements
     * @param string $bankTransactionId
     * @param string|null $statusDescription
     */
    public function markChunkDistributing(Collection $subSettlements, string $bankBatchId, ?string $statusDescription = null): void
    {
        $now = Carbon::now();
        SubSettlement::whereIn('id', $subSettlements->pluck('id'))
            ->update([
                'status' => SubSettlement::STATUS_DISTRIBUTING,
                'bank_batch_id' => $bankBatchId,
                'status_description' => $statusDescription ?? '批量代发处理中',
                'distribute_started_at' => $now,
            ]);
    }

    /**
     * 批量更新为发放失败并回滚余额。
     *
     * @param Settlement $settlement
     * @param Collection<int, SubSettlement> $subSettlements
     * @param Transaction $transaction
     * @param string $reason
     */
    public function markChunkFailed(
        Settlement $settlement,
        Collection $subSettlements,
        Transaction $transaction,
        string $reason
    ): void {
        DB::transaction(function () use ($settlement, $subSettlements, $transaction, $reason) {
            $tenant = Tenant::whereKey($settlement->tenant_id)->lockForUpdate()->firstOrFail();

            $amount = $subSettlements->sum(function (SubSettlement $item) {
                return (float) $item->commission;
            });
            $amountFormatted = number_format($amount, 2, '.', '');

            SubSettlement::whereIn('id', $subSettlements->pluck('id'))
                ->update([
                    'status' => SubSettlement::STATUS_DISTRIBUTE_FAILED,
                    'status_description' => $reason,
                    'bank_batch_id' => null,
                    'distribute_started_at' => null,
                ]);

            $tenant->account_balance = bcadd($tenant->account_balance, $amountFormatted, 2);
            $tenant->save();

            $transaction->update([
                'status' => Transaction::STATUS_FAILED,
                'balance_after' => $tenant->account_balance,
                'description' => $reason,
                'processed_at' => Carbon::now(),
            ]);
        });
    }

    /**
     * 记录交易为已完成。
     */
    public function markTransactionCompleted(Transaction $transaction): void
    {
        $transaction->update([
            'status' => Transaction::STATUS_COMPLETED,
            'processed_at' => Carbon::now(),
        ]);
    }

    /**
     * 部分子结算失败时回滚对应金额并记录退款流水。
     *
     * @param Settlement $settlement
     * @param Collection<int, SubSettlement> $subSettlements
     * @param string $reason
     * @param int $initiatorId
     */
    public function refundFailedSubSettlements(
        Settlement $settlement,
        Collection $subSettlements,
        string $reason,
        int $initiatorId
    ): void {
        if ($subSettlements->isEmpty()) {
            return;
        }

        DB::transaction(function () use ($settlement, $subSettlements, $reason, $initiatorId) {
            $tenant = Tenant::whereKey($settlement->tenant_id)->lockForUpdate()->firstOrFail();
            $amount = $subSettlements->sum(function (SubSettlement $item) {
                return (float) $item->commission;
            });
            $amountFormatted = number_format($amount, 2, '.', '');

            $tenant->account_balance = bcadd($tenant->account_balance, $amountFormatted, 2);
            $tenant->save();

            Transaction::create([
                'tenant_id' => $settlement->tenant_id,
                'type' => Transaction::TYPE_INCOME,
                'sub_type' => Transaction::SUB_TYPE_BALANCE_REFUND,
                'amount' => $amountFormatted,
                'balance_after' => $tenant->account_balance,
                'landing_company' => $settlement->landing_company ?? '未配置落地公司',
                'status' => Transaction::STATUS_COMPLETED,
                'summary' => sprintf('结算单 %s 发放失败退款', $settlement->settlement_number),
                'description' => $reason,
                'metadata' => [
                    'settlement_id' => $settlement->id,
                    'settlement_number' => $settlement->settlement_number,
                    'refunded_sub_settlement_ids' => $subSettlements->pluck('id')->all(),
                ],
                'transaction_time' => Carbon::now(),
                'created_by' => $initiatorId,
            ]);
        });
    }
}
